<!--  -->
<template>
  <div style="height: 100vh; width: 100vw">
    <div ref="viewport" class="viewport">
      <div class="display" ref="display" tabindex="0"/>
    </div>

    <el-drawer :with-header="false" title="终端辅助操作" :visible.sync="drawer" direction="rtl" style="padding: 5px;"
               @close="installKeyboard">
      <div style="text-align: center">
        <h1 style="margin: 20px 0;">终端辅助操作</h1>
        <div style="padding: 10px;">
          <el-upload ref="upload" class="upload-demo" :http-request="fileupload" drag action="" multiple>
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
            <div class="el-upload__tip" slot="tip">将上传至远程服务器GuacamoleRDP上的GUACFS</div>
            <el-progress v-show="progress" :percentage=upfilePercent></el-progress>
          </el-upload>
        </div>
        <div style="margin: 20px;text-align: left;">
          <span style="font-weight: bold;">下载文件：</span>
          <span style="font-size: small;line-height: 200%">请在远程终端中的文件管理器中，直接将文件粘贴到"GUACFS"盘符中的download文件夹将自动下载</span>
        </div>
      </div>


      <!-- <div style="padding: 10px;text-align: left;">
          <span style="font-size: 20px ;font-weight: bold;">剪切板:</span><i class="el-icon-info"></i>
          <span>点击发送,即可将内容发送至远程终端剪切板</span>
          <el-input clearable autosize type="textarea" :rows="8" v-model="textarea" placeholder=""></el-input>
      </div>
      <el-button size="small" type="">清空</el-button>
      <el-button size="small" @click="setClipboard(textarea)" type="success">发送</el-button> -->
    </el-drawer>
  </div>
</template>

<script>
  import Guacamole from "guacamole-common-js";
  import GuacMouse from "../libs/GuacMouse";
  import FileSaver from "file-saver";
  import states from "../libs/states";
  import clipboard from "../libs/clipboard";

  Guacamole.Mouse = GuacMouse.mouse;

  function serialize(obj) {
    let str = [];
    for (const p in obj)
      if (obj[p]) {
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
      }
    return str.join("&");
  }

  export default {
    name: "remote_view",
    layout: 'nothing',
    data() {
      return {
        connected: false,
        viewport: null,
        client: null,
        keyboard: null,
        drawer: false,
        display: null,
        textarea: '',
        connectionState: states.IDLE,
        mouse: null,
        errorMessage: "",
        upfilePercent: 0,
        progress: false,
        query: {
          screen_width: 1024,
          screen_height: 768,
          screen_dpi: 128,
        },
      };
    },
    created() {

    },
    watch: {
      connectionState(state) {
        this.$notify({
          title: state,
          type: "success",
        });
      },
    },
    components: {},

    computed: {},

    mounted() {
      var _self = this
      document.title = this.$route.query.title;
      this.setScreenSize()
      const routerParams = this.$route.query;
      var curPath = window.document.location.host;
      let url = "wss://" + curPath + "?token=" + routerParams.token
      // let url = "ws://127.0.0.1:9090/?token=" + routerParams.token
    //  console.log(url)
      let tunnel = new Guacamole.WebSocketTunnel(url);
      //     if (this.client) {
      //         this.display.scale(0);
      //         this.uninstallKeyboard()
      //         this.uninstallKeyboard()
      //     }
      this.client = new Guacamole.Client(tunnel);
      tunnel.onerror = (status) => {
        // eslint-disable-next-line no-console
        console.error(`Tunnel failed ${JSON.stringify(status)}`);
        this.connectionState = states.TUNNEL_ERROR;
      };
      clipboard.install(this.client);
      tunnel.onstatechange = (state) => {
        switch (state) {
          // Connection is being established
          case Guacamole.Tunnel.State.CONNECTING:
            this.connectionState = states.CONNECTING;
            break;
          // Connection is established / no longer unstable
          case Guacamole.Tunnel.State.OPEN:
            this.connectionState = states.CONNECTED;
            break;
          // Connection is established but misbehaving
          case Guacamole.Tunnel.State.UNSTABLE:
            // TODO
            //this.$message.error("通道不稳定");
            console.log("通道不稳定")
            break;
          // Connection has closed
          case Guacamole.Tunnel.State.CLOSED:
            this.connectionState = states.DISCONNECTED;
            break;
        }
      };
      this.client.onstatechange = (clientState) => {
        switch (clientState) {
          case 0:
            this.connectionState = states.IDLE;
            break;
          case 1:
            // connecting ignored for some reason?
            break;
          case 2:
            this.connectionState = states.WAITING;
            break;
          case 3:
            this.connectionState = states.CONNECTED;
            window.addEventListener("resize", _self.resize);
            this.$refs.viewport.addEventListener("mouseenter", _self.resize);
            clipboard.setRemoteClipboard(_self.client);
          // eslint-disable-next-line no-fallthrough
          case 4:
          case 5:
            // disconnected, disconnecting
            break;
        }
      };
      this.client.onerror = (error) => {

        _self.client.disconnect();
        // eslint-disable-next-line no-console
        _self.$message.error(`客户端发生错误 ${JSON.stringify(error)}`);
        _self.errorMessage = error.message;
        _self.connectionState = states.CLIENT_ERROR;
      };

      this.display = this.client.getDisplay();
      const displayElm = this.$refs.display;
      this.client.onclipboard = clipboard.onClipboard;
      displayElm.appendChild(this.client.getDisplay().getElement());
      displayElm.addEventListener("contextmenu", (e) => {
        e.stopPropagation();
        if (e.preventDefault) {
          e.preventDefault();
        }
        e.returnValue = false;
      });

      //全占VIEWPORT屏幕
      var pixel_density = window.devicePixelRatio || 1;
      var optimal_dpi = pixel_density * 96;
      var optimal_width = this.viewport.clientWidth -20//* pixel_density;
      var optimal_height = this.viewport.clientHeight-10// * pixel_density;
      //居中设置
      var $wrapper = this.client.getDisplay().getElement()
      $wrapper.style.position = 'absolute'
      $wrapper.style.left = '50%'
      $wrapper.style.top = '50%'
      $wrapper.style.transform = 'translate(-50%,-50%)';
      let param = serialize(this.query);
      this.client.connect(encodeURI(url+'&width='+optimal_width+'&height='+optimal_height+'&dpi='+optimal_dpi));


      // Disconnect on close
      window.onunload = () => this.client.disconnect();


      //Mouse  鼠标事件
      this.mouse = new Guacamole.Mouse(displayElm);
      //Hide software cursor when mouse leaves display
      this.mouse.onmouseout = () => {
        if (!this.display) return;
        this.display.showCursor(false);
      };
      // allows focusing on the display div so that keyboard doesn't always go to session
      displayElm.onclick = () => {
        displayElm.focus();
      };
      displayElm.onfocus = () => {
        displayElm.className = "focus";
      };
      displayElm.onblur = () => {
        displayElm.className = "";
      };
      this.mouse.onmousedown =
        this.mouse.onmouseup =
          this.mouse.onmousemove =
            this.handleMouseState;
      // setTimeout(() => {
      //     _self.resize();
      //     displayElm.focus();
      // }, 1000); // $nextTick wasn't enough

      //Keyboard 键盘事件
      window.addEventListener('keydown', function (e) {
        var ctrl = e.ctrlKey,
          shift = e.shiftKey,
          alt = e.altKey;
        if (ctrl && shift && alt) {
          _self.drawer = true
          _self.$nextTick(() => {
            const input = document.getElementById('file-input');
            if (input) {
              input.onchange = function () {
                const file = input.files[0];
                //上传开始
                _self.uploadFile(file);
              };
            }
          })
        }
      })
      this.keyboard = new Guacamole.Keyboard(displayElm);
      this.installKeyboard();


      //RDP文件下载事件
      this.client.onfile = function (stream, mimetype, filename) {
        //通知服务端，已经收到了stream
        stream.sendAck("OK", Guacamole.Status.Code.SUCCESS);
        //开始处理输入流，这里封装了一个downloadFile方法
        _self.downloadFile(stream, mimetype, filename);
      };
      //     //剪切板复制
      //     //监听堡垒机端往剪切板复制事件，然后写入文本框中
      //     // client.onclipboard = function (stream, mimetype) {
      //     //     if (/^text\//.exec(mimetype)) {
      //     //         var stringReader = new Guacamole.StringReader(stream);
      //     //         var json = "";
      //     //         stringReader.ontext = function ontext(text) {
      //     //             json += text
      //     //         }

      //     //         stringReader.onend = function () {
      //     //             // var clipboardElement = document.getElementById("clipboard");
      //     //             // clipboardElement.value = '';
      //     //             // clipboardElement.value = json;
      //     //             var tag = document.createElement('input');
      //     //             tag.setAttribute('id', 'cp_hgz_input');
      //     //             tag.value = '';
      //     //             tag.value = json;
      //     //             document.getElementsByTagName('body')[0].appendChild(tag);
      //     //             document.getElementById('cp_hgz_input').select();
      //     //             document.execCommand('copy');
      //     //             document.getElementById('cp_hgz_input').remove();
      //     //             _self.$message('复制到剪切板')
      //     //         }
      //     //     }
      //     // }

    },


    methods: {
      setScreenSize() {
        const elm = this.viewport = this.$refs.viewport;
        if (!elm || !elm.offsetWidth) {
          // resize is being called on the hidden window
          return;
        }
        let pixelDensity = window.devicePixelRatio || 1;
        const width = elm.clientWidth * pixelDensity;
        const height = elm.clientHeight * pixelDensity;
        this.query.screen_height = height;
        this.query.screen_width = width;
      },
      handleMouseState(mouseState) {
        var viewport = this.$refs.viewport
        var $wrapper = this.client.getDisplay().getElement()
        let height1 = viewport.offsetHeight
        let width1 = viewport.clientWidth

        let height = $wrapper.clientHeight
        let width = $wrapper.clientWidth

        const scaledMouseState = Object.assign({}, mouseState, {
          // x: mouseState.x / this.display.getScale(),
          x: (mouseState.x - (width1 - width) / 2) / this.display.getScale(),
          //y: mouseState.y / this.display.getScale(),
          y: (mouseState.y - (height1 - height) / 2) / this.display.getScale(),
        });

        this.client.sendMouseState(scaledMouseState);
      },

      resize() {
        const elm = this.$refs.viewport;
        if (!elm || !elm.offsetWidth) {
          // resize is being called on the hidden window
          return;
        }
        let pixelDensity = window.devicePixelRatio || 1;
        const width = elm.clientWidth * pixelDensity;
        const height = elm.clientHeight * pixelDensity;
        //居中显示
        // var $wrapper = this.client.getDisplay().getElement()
        // $wrapper.style.position = 'absolute'
        // $wrapper.style.left = '50%'
        // $wrapper.style.top = '50%'
        // $wrapper.style.transform = 'translate(-50%,-50%)';

        if (
          this.display.getWidth() !== width ||
          this.display.getHeight() !== height
        ) {
          this.client.sendSize(width, height);
        }
        // setting timeout so display has time to get the correct size
        setTimeout(() => {
          const scale = Math.min(
            elm.clientWidth / Math.max(this.display.getWidth(), 1),
            elm.clientHeight / Math.max(this.display.getHeight(), 1)
          );
          this.display.scale(scale);
        }, 100);
      },
      downloadFile(stream, mimetype, filename) {

        //拿到的流不能直接使用，先实例化一个处理器，使用blob reader处理数据
        var blob_builder;
        if (window.BlobBuilder) blob_builder = new window.BlobBuilder();
        else if (window.WebKitBlobBuilder)
          blob_builder = new window.WebKitBlobBuilder();
        else if (window.MozBlobBuilder) blob_builder = new window.MozBlobBuilder();
        else
          blob_builder = new (function () {
            var blobs = [];

            /** @ignore */
            this.append = function (data) {
              blobs.push(new Blob([data], {type: mimetype}));
            };

            /** @ignore */
            this.getBlob = function () {
              return new Blob(blobs, {type: mimetype});
            };
          })();

        // Append received blobs
        stream.onblob = function (data) {
          // Convert to ArrayBuffer
          var binary = window.atob(data);
          var arrayBuffer = new ArrayBuffer(binary.length);
          var bufferView = new Uint8Array(arrayBuffer);

          for (var i = 0; i < binary.length; i++)
            bufferView[i] = binary.charCodeAt(i);

          //收到后就交给blob_builder
          blob_builder.append(arrayBuffer);
          //length += arrayBuffer.byteLength;

          // Send success response
          stream.sendAck("OK", 0x0000);
        };

        stream.onend = function () {
          //结束的时候，获取blob_builder里面的可用数据
          var blob_data = blob_builder.getBlob();

          //数据传输完成后进行下载等处理
          if (mimetype.indexOf("stream-index+json") != -1) {
            //如果是文件夹,使用filereader读取blob数据，可以获得该文件夹下的文件和目录的名称和类型，是一个json形式
            var blob_reader = new FileReader();
            blob_reader.addEventListener("loadend", function () {
              let folder_content = JSON.parse(blob_reader.result);
              //重新组织当前文件目录，appendFileItem是自己封装的文件系统动态展示
              window.appendFileItem(folder_content);
              //$("#header_title").text(filename);
            });
            blob_reader.readAsBinaryString(blob_data);
          } else {
            //如果是文件，直接下载，但是需要解决个问题，就是如何下载blob数据
            //借鉴了https://github.com/eligrey/FileSaver.js这个库
            var file_arr = filename.split("/");
            var download_file_name = file_arr[file_arr.length - 1];
            FileSaver.saveAs(blob_data, download_file_name);
          }
        };
      },

      uploadFile(params) {
        const _this = this;
        const file = params.file
        const fileUpload = {};
        const reader = new FileReader();
        var STREAM_BLOB_SIZE = 4096;
        reader.onloadend = function fileContentsLoaded() {
          const stream = _this.client.createFileStream(file.type, file.name);
          const bytes = new Uint8Array(reader.result);

          let offset = 0;
          let progress = 0;

          fileUpload.name = file.name;
          fileUpload.mimetype = file.type;
          fileUpload.length = bytes.length;

          stream.onack = function ackReceived(status) {
            if (status.isError()) {
              console.log('Error uploading file');

              return false;
            }

            const slice = bytes.subarray(offset, offset + STREAM_BLOB_SIZE);
            const base64 = _this.bufferToBase64(slice);

            // Write packet
            stream.sendBlob(base64);

            // Advance to next packet
            offset += STREAM_BLOB_SIZE;

            if (offset >= bytes.length) {
              progress = 100;
              stream.sendEnd();
            } else {
              progress = Math.floor(offset / bytes.length * 100);
            }

            // Send progress to file upload's popup

            _this.upfilePercent = progress;
            if (progress == 100) {
              _this.progress = false
              _this.$message.success("上传成功")
              _this.$refs.upload.clearFiles()
            }


            // _this.uploadProgress.setProgress({
            //     name: file.name,
            //     progress: progress
            // });
          }
        };

        reader.readAsArrayBuffer(file);

        return fileUpload;
      },
      fileupload(params) {
        this.progress = true
        this.uploadFile(params)
      },
      bufferToBase64(buf) {
        var binstr = Array.prototype.map.call(buf, function (ch) {
          return String.fromCharCode(ch);
        }).join('');
        return btoa(binstr);
      },
      //将内容传送到往堡垒机，data是获取到的文本框中的内容
      setClipboard(data) {
        var stream = this.client.createClipboardStream("text/plain");

        var writer = new Guacamole.StringWriter(stream);
        for (var i = 0; i < data.length; i += 4096) {
          writer.sendText(data.substring(i, i + 4096));
        }

        writer.sendEnd();
      },
      installKeyboard() {

        this.keyboard.onkeydown = (keysym) => {

          this.client.sendKeyEvent(1, keysym);
        };
        this.keyboard.onkeyup = (keysym) => {
          this.client.sendKeyEvent(0, keysym);
        };
      },
      uninstallKeyboard() {
        this.keyboard.onkeydown = this.keyboard.onkeyup = () => {
        };
      },

    },
  };
</script>
<style lang='less' scoped>
  .display {
    overflow: hidden;
    width: 100%;
    height: 100%;
  }

  .viewport {
    background-color: coral;
    border-color: blue;
    border-width: 1px;
    position: relative;
    width: 100%;
    height: 100%;
  }
</style>
